# This file is part of OrangeSpider. OrangeSpider is a meta model based
# development tool.
# 
# OrangeSpider is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# OrangeSpider is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with OrangeSpider.  If not, see <http://www.gnu.org/licenses/>.
#
# Copyright 2009 Stefan Nikolaus <OrangeSpiderMaster@googlemail.com>

import time
import cPickle
#import pickle
import random
import Types

OPTIONAL =     "OPTIONAL"
ONE      =     "ONE"
MANY     =     "MANY"

OPT_TO_OPT = OPTIONAL + ":" + OPTIONAL
OPT_TO_ONE = OPTIONAL + ":" + ONE
OPT_TO_MANY = OPTIONAL + ":" + MANY

ONE_TO_OPT = ONE + ":" + OPTIONAL
ONE_TO_ONE = ONE + ":" + ONE
ONE_TO_MANY = ONE + ":" + MANY

MANY_TO_OPT = MANY + ":" + OPTIONAL
MANY_TO_ONE = MANY + ":" + ONE
MANY_TO_MANY = MANY + ":" + MANY


class MetaAtomAggregation(dict):
    pass
class MetaTypeAggregation(dict):
    pass
class MetaPropertyAggregation(dict):
    pass
class MetaClassAggregation(dict):
    pass
class MetaAssocAggregation(dict):
    pass
class OutgoingMetaAssocCollection(dict):
    def metaAssocs(self):
      return sorted(self.itervalues())
    def metaClasses(self):
      for a in sorted(self.itervalues()):
         yield a.target
class IncomingMetaAssocCollection(dict):
    def metaAssocs(self):
      return sorted(self.itervalues())
    def metaClasses(self):
      for a in sorted(self.itervalues()):
         yield a.source
class ClassAggregation(dict):
    pass
class AssocAggregation(dict):
    pass
class AssocMap(dict):
    pass
class OutgoingAssocCollection(dict):
    def assocs(self):
      return sorted(self.itervalues())
    def classes(self):
      for a in sorted(self.itervalues()):
         yield a.target
class IncomingAssocCollection(dict):
    def assocs(self):
      return sorted(self.itervalues())
    def classes(self):
      for a in sorted(self.itervalues()):
         yield a.source
    

class Entity(dict):
    METAMODEL = 1
    METACLASS = 2
    METAASSOC = 3
    METAPROPERTY = 4
    METATYPE = 5
    METAENUM = 6
    METAATOM = 7
    CLASS = 8
    ASSOC = 9

    _gIsLoading = 0

    def __init__(self, parent):
        self.parent = parent
    def isMetaEntity(self):
        return 0
    def isMetaAtom(self):
        return 0
    def isMetaType(self):
        return 0
    def isMetaEnum(self):
        return 0
    def isMetaProperty(self):
        return 0
    def isMetaClass(self):
        return 0
    def isMetaAssoc(self):
        return 0
    def isExtensionEntity(self):
        return 0
    def isClass(self):
        return 0
    def isAssoc(self):
        return 0
    def isMetaModel(self):
        return 0

    def typ(self):
        assert(0)
    def metaModel(self):
        return self.parent
    def __setitem__(self, key, newValue):
        if Entity._gIsLoading:
            dict.__setitem__(self, key, newValue)
            return
        r = self.metaModel()
        if r.watcher:
            oldValue = self.get(key)
            dict.__setitem__(self, key, newValue)
            r.watcher.propertyChanged(self, key, oldValue, newValue)
        else:
            dict.__setitem__(self, key, newValue)

    def identifierPath(self):
        return None

    def state(self):
        t = (self.typ(), self.identifierPath(), [self.copy()] )
        return t

class MetaEntity(Entity):
    def __init__(self, name, parent, description = ""):
        Entity.__init__(self, parent)
        self["Name"] = name
        self["Description"] = description
    def isMetaEntity(self):
        return 1
    def identifierPath(self):
        return self["Name"]
    def __setitem__(self, key, newValue):
        if key == "Name":
            # don't log changes for the name
            dict.__setitem__(self, key, newValue)
        else:
            Entity.__setitem__(self, key, newValue)

class MetaAtom(MetaEntity):
    def __init__(self, name, parent, description = ""):
        MetaEntity.__init__(self, name, parent, description)
        assert(parent.isMetaEnum())
    def isMetaAtom(self):
        return 1
    def metaModel(self):
        return self.parent.parent
    def typ(self):
        return Entity.METAATOM
    def identifierPath(self):
        return (self.parent["Name"], self["Name"])


class MetaType(MetaEntity):
    def __init__(self, name, parent, description = ""):
        MetaEntity.__init__(self, name, parent, description)
        self["DefaultValue"] = ""
        assert(parent.isMetaModel())
    def isMetaType(self):
        return 1
    def typ(self):
        return Entity.METATYPE
    def defaultValue(self):
        return Types.getTypedValue(self, self["DefaultValue"])

class MetaEnum(MetaType):
    def __init__(self, name, parent, description = ""):
        MetaType.__init__(self, name, parent, description)
        self["DefaultValue"] = ""
        self.metaAtoms = MetaAtomAggregation()
    def isMetaEnum(self):
        return 1
    def eachMetaAtom(self):
        return self.metaAtoms.itervalues()
    def typ(self):
        return Entity.METAENUM
    def defaultValue(self):
        return self["DefaultValue"]

class MetaProperty(MetaEntity):
    def __init__(self, name, parent, metaType, description = ""):
        MetaEntity.__init__(self, name, parent, description)
        self.metaType = metaType
        self["IsUnique"] = 0
        self["IsIdentifying"] = 0
        if name == "Name":
            self["IsIdentifying"] = 1
        assert(metaType.isMetaType())
    def isMetaProperty(self):
        return 1
    def metaModel(self):
        return self.parent.parent
    def typ(self):
        return Entity.METAPROPERTY
    def identifierPath(self):
        return (self.parent["Name"], self["Name"], self.metaType["Name"])

class MetaClass(MetaEntity):
    def __init__(self, name, parent, description = ""):
        MetaEntity.__init__(self, name, parent, description)
        assert(parent.isMetaModel())
        self.metaProperties = MetaPropertyAggregation()
        self.extension = ClassAggregation()
        self.outgoing = OutgoingMetaAssocCollection()
        self.incoming = IncomingMetaAssocCollection()
        self["Icon"] = ""
        
    def isMetaClass(self):
        return 1
    def typ(self):
        return Entity.METACLASS
    def eachMetaProperty(self):
        return self.metaProperties.itervalues()
    def eachOutgoingMetaAssoc(self): 
        return self.outgoing.metaAssocs()
    def eachIncomingMetaAssoc(self):
        return self.incoming.metaAssocs()
    def eachOutgoingMetaClass(self):
        return self.outgoing.metaClasses()
    def eachIncomingMetaClass(self):
        return self.incoming.metaClasses()
    def eachClass(self):
        return self.extension.itervalues()

class MetaAssoc(MetaEntity):
    def __init__(self, name, parent, source, target, description = "", 
                 cardinality = MANY_TO_MANY,
                 aggregation = 0):
        MetaEntity.__init__(self, name, parent, description)
        assert(parent.isMetaModel())
        assert(source.isMetaClass())
        assert(target.isMetaClass())
        self["SourceName"] = ""
        self["TargetName"] = ""
        self["AssocName"] = ""
        cards = cardinality.split(":")
        self["SourceCardinality"] = cards[0]
        self["TargetCardinality"] = cards[1]
        self["Aggregation"] = aggregation
        self.metaProperties = MetaPropertyAggregation()
        self.extension = AssocAggregation()
        self.source = source
        self.target = target
        self["Icon"] = ""

    def isMetaAssoc(self):
        return 1
    def typ(self):
        return Entity.METAASSOC

    def eachMetaProperty(self):
        return self.metaProperties.itervalues()
    def eachAssoc(self):
        return self.extension.itervalues()

class ExtensionEntity(Entity):
    def __init__(self, parent):
        Entity.__init__(self, parent)
        self.intention = parent
    def metaModel(self):
        return self.parent.parent
    def isExtensionEntity(self):
        return 1
    def identifierPath(self):
        return (self.parent["Name"], self["Id"])
    def __setitem__(self, key, newValue):
        if key == "Id":
            # don't log changes for the id
            dict.__setitem__(self, key, newValue)
        else:
            Entity.__setitem__(self, key, newValue)

class Class(ExtensionEntity):
    def __init__(self, parent):
        ExtensionEntity.__init__(self, parent)
        assert(parent.isMetaClass())
        self.incoming = AssocMap()
        self.outgoing = AssocMap()
        
    def isClass(self):
        return 1
    def typ(self):
        return Entity.CLASS

    def eachIncomingAssoc(self, metaAssocName):
        return self.incoming[metaAssocName].assocs()
    def eachIncomingClass(self, metaAssocName):
        return self.incoming[metaAssocName].classes()
    def eachOutgoingAssoc(self, metaAssocName):
        return self.outgoing[metaAssocName].assocs()
    def eachOutgoingClass(self, metaAssocName):
        return self.outgoing[metaAssocName].classes()
    def firstIncomingClass(self, metaAssocName):
        for c in self.eachIncomingClass(metaAssocName):
            return c
        return None
    def firstOutgoingClass(self, metaAssocName):
        for c in self.eachOutgoingClass(metaAssocName):
            return c
        return None
    def findOutgoingClass(self, metaAssocName, 
                          metaPropertyName, value):
        for c in self.eachOutgoingClass(metaAssocName):
            if c[metaPropertyName] == value:
                 return c
        return None
    def findIncomingClass(self, metaAssocName, 
                          metaPropertyName, value):
        for c in self.eachIncomingClass(metaAssocName):
            if c[metaPropertyName] == value:
                 return c
        return None

class Assoc(ExtensionEntity):
    def __init__(self, parent):
        ExtensionEntity.__init__(self, parent)
        assert(parent.isMetaAssoc())
        self.source = None
        self.target = None
        
    def isAssoc(self):
        return 1
    def typ(self):
        return Entity.ASSOC

    def state(self):
        t = (self.typ(), self.identifierPath(), [self.copy(),
             self.source.identifierPath(), self.target.identifierPath()] )
        return t

# Definition of the listener interface
class Listener:
    def propertyChanged(self, object, key, oldValue, newValue):
        pass
    def entityCreated(self, object):
        pass
    def entityDeleted(self, object):
        pass

class Watcher:
    def __init__(self):
        self.stack = None
        self._listeners = {}
        self.isOn = 1
    def setOn(self):
        self.isOn = 1
    def setOff(self):
        self.isOn = 0
    def addListener(self, listener, name):
        if name in self._listeners:
            return self._listeners[name]
        else:
            self._listeners[name] = listener
            return listener
    def removeListener(self, name):
        del self._listeners[name]
    def listeners(self):
        return self._listeners
    def propertyChanged(self, object, key, oldValue, newValue):
        if self.isOn:
            if not self.stack is None:
                self.stack.pushChanged(object, key, oldValue, newValue)
            for listener in self._listeners.itervalues():
                listener.propertyChanged(object, key, oldValue, newValue)

    def entityCreated(self, object):
        if self.isOn:
            if not self.stack is None:
                self.stack.pushCreated(object)
            for listener in self._listeners.itervalues():
                listener.entityCreated(object)

    def entityDeleted(self, object):
        if self.isOn:
            if not self.stack is None:
                self.stack.pushDeleted(object)
            for listener in self._listeners.itervalues():
                listener.entityDeleted(object)

class UndoStack(list):
    EntityCreated = 1
    PropertyChanged = 2
    EntityDeleted = 3

    def __init__(self):
        pass
    def pushCreated(self, object):
        t = (UndoStack.EntityCreated, object.state())
        self.append(t)
        
    def pushChanged(self, object, key, oldValue, newValue):
        if key in object:
            t = (UndoStack.PropertyChanged,
                 (object.typ(), object.identifierPath(), key, oldValue, newValue))
            self.append(t)

    def pushDeleted(self, object):
        t = (UndoStack.EntityDeleted, object.state())
        self.append(t)

    def pop(self):
        return list.pop(self)

class TransactionManager:
    def __init__(self, parent):
        self.parent = parent
        self.currentMark = 0
        self.marks = {}
    def metaModel(self):
        return self.parent
    def watcher(self):
        return self.parent.watcher

    def begin(self):
        # Setup an undo stack for logging changes
        self.undoStack = UndoStack()
        self.watcher().stack = self.undoStack

    def commit(self):
        # Commit means: No undo possible
        self.undoStack = None
        self.watcher().stack = None

    def rollback(self, toMark = 0):
        # For rollback: Don't log changes
        self.watcher().stack = None
        rollback = Rollback(self.metaModel(), self.undoStack)
        rollback.execute(toMark)

    def mark(self):
        self.currentMark += 1
        self.marks[currentMark] = len(self.stack) - 1
        return self.currentMark

class Rollback:
    def __init__(self, metaModel, undoStack):
        self.metaModel = metaModel
        self.undoStack = undoStack

    def execute(self, toMark = 0):
        while len(self.undoStack) > 0:
            action, change = self.undoStack.pop()
            if action == UndoStack.EntityCreated:
                typ, currentId, state = change
                object = self.metaModel.access(typ, currentId)
                self.metaModel.delete(object)
                        
            elif action == UndoStack.PropertyChanged:
                typ, currentId, key, oldValue, newValue = change
                object = self.metaModel.access(typ, currentId)
                object[key] = oldValue

            elif action == UndoStack.EntityDeleted:
                typ, currentId, state = change
                self.metaModel.create(typ, currentId, state)
                                      
            else:
                assert(0) 
    
class MetaModelDeleter:
    def __init__(self, parent):
        self.parent = parent
        assert(parent.isMetaModel())

    def metaModel(self):
        return self.parent
    def watcher(self):
        return self.parent.watcher

    def delete(self, object):
        if object.isMetaClass():
            self.deleteMetaClass(object["Name"])
        elif object.isMetaAssoc():
            self.deleteMetaAssoc(object["Name"])
        elif object.isMetaType():
            self.deleteMetaType(object["Name"])
        elif object.isMetaAtom():
            self.deleteMetaAtom(object.parent["Name"],
                                object["Name"])
        elif object.isMetaProperty():
            self.deleteMetaProperty(object.parent,
                                    object["Name"])
        elif object.isClass():
            self.deleteClass(object)
        elif object.isAssoc():
            self.deleteAssoc(object)

    def deleteMetaClass(self, name):
        mc = self.metaModel().metaClasses[name]

        # all outgoing metaassocs: delete
        keys = mc.outgoing.keys()
        for key in keys:
            self.deleteMetaAssoc(key)

        # all incoming metaassocs: delete
        keys = mc.incoming.keys()
        for key in keys:
            self.deleteMetaAssoc(key)

        # delete classes
        keys = mc.extension.keys()
        for key in keys:
            cls = mc.extension[key]
            self.deleteClass(cls)

        # delete meta properties
        keys = mc.metaProperties.keys()
        for key in keys:
            self.deleteMetaProperty(mc, key)

        # delete meta class
        self._entityDeleted(mc)
        del self.metaModel().metaClasses[name]

        #oldself._entityDeleted(mc)

    def deleteMetaAssoc(self, name):
        ma = self.metaModel().metaAssocs[name]

        # delete assocs
        keys = ma.extension.keys()
        for key in keys:
            assoc = ma.extension[key]
            self.deleteAssoc(assoc)

        # delete meta properties
        keys = ma.metaProperties.keys()
        for key in keys:
            self.deleteMetaProperty(ma, key)

        # delete meta assoc
        self._entityDeleted(ma)
        del self.metaModel().metaAssocs[name]

        #oldself._entityDeleted(ma)

    def deleteMetaType(self, name):
        # delete meta type / enum, if not used
        mt = self.metaModel().metaTypes[name]
        used = 0
        for mc in self.metaModel().eachMetaClass():
            for mp in mc.eachMetaProperty():
                if mp.metaType is mt:
                    used = 1
                    break
        for ma in self.metaModel().eachMetaClass():
            for mp in ma.eachMetaProperty():
                if mp.metaType is mt:
                    used = 1
                    break

        if not used:
            del self.metaModel().metaTypes[name]
            self._entityDeleted(mt)

    def deleteMetaAtom(self, enumName, name):
        # delete meta atom
        me = self.metaModel().metaTypes[enumName]
        mat = me.metaAtoms[name]
        self._entityDeleted(mat)
        del me.metaAtoms[name]
        #oldself._entityDeleted(mat)

    def deleteMetaProperty(self, mo, name):
        # delete properties of all classes
        for o in mo.extension:
            del o[name]

        # delete meta property
        mp = mo.metaProperties[name]
        self._entityDeleted(mp)
        del mo.metaProperties[name]

        #oldself._entityDeleted(mp)

    def deleteClass(self, cls, recursive = 1):
        if recursive:
            classList = []
            self._createClassList(cls, classList)
            for c in classList:
                self._deleteClass(c)
        else:
            self._deleteClass(cls)

    def _createClassList(self, cls, classList):
        if cls in classList:
            # already in list
            return

        mc = cls.intention

        # look for all outgoing assocs with aggregation = 1
        for ma in mc.eachOutgoingMetaAssoc():
            if ma["Aggregation"]:
                for c in cls.eachOutgoingClass(ma["Name"]):
                    self._createClassList(c, classList)

        # schedule for deletion
        classList.append(cls)

    def _deleteClass(self, cls):
        mc = cls.intention
        currentId = cls["Id"]

        # all outgoing assocs: delete
        for ma in mc.eachOutgoingMetaAssoc():
            outgoingAssocCollection = cls.outgoing[ma["Name"]]
            keys = outgoingAssocCollection.keys()
            for key in keys:
                assoc = outgoingAssocCollection[key]
                self.deleteAssoc(assoc)

        # all incoming assocs: delete
        for ma in mc.eachIncomingMetaAssoc():
            incomingAssocCollection = cls.incoming[ma["Name"]]
            keys = incomingAssocCollection.keys()
            for key in keys:
                assoc = incomingAssocCollection[key]
                self.deleteAssoc(assoc)

        # metaclass.extension delete
        self._entityDeleted(cls)
        del mc.extension[currentId]

        #oldself._entityDeleted(cls)

    def deleteAssoc(self, assoc):
        ma = assoc.intention
        currentId = assoc["Id"]

        # assoc.source.outgoing delete
        del assoc.source.outgoing[ma["Name"]][currentId]

        # assoc.target.incoming delete
        del assoc.target.incoming[ma["Name"]][currentId]

        # metaassoc.extension delete
        self._entityDeleted(assoc)
        del ma.extension[currentId]

        #oldself._entityDeleted(assoc)

    def _entityDeleted(self, object):
        if self.watcher():
            self.watcher().entityDeleted(object)

class MetaModelCreator:
    def __init__(self, parent):
        self.parent = parent
        assert(parent.isMetaModel())

    def metaModel(self):
        return self.parent
    def watcher(self):
        return self.parent.watcher

    def MetaClass(self, name, description = ""):
        self._setWatcherOff()
        result = MetaClass(name, self.metaModel(), description)
        self.metaModel().metaClasses[name] = result
        self._setWatcherOn()
        self._entityCreated(result)
        self.MetaProperty(name, "Id", "Integer", 
                          "The unique identifier")
        return result

    def MetaAssoc(self, name, description = "", 
                  cardinality = MANY_TO_MANY,
                  aggregation = False):
        names = name.split(".")
        result = None
        if len(names) == 3:
            strSource = names[0]
            strTarget = names[2]
            source = self.metaModel().metaClasses[strSource]
            assert(source)
            target = self.metaModel().metaClasses[strTarget]
            assert(target)
            self._setWatcherOff()
            result = MetaAssoc(name, self.metaModel(), source, target, 
                               description, cardinality, aggregation)
            self.metaModel().metaAssocs[name] = result
            self._setWatcherOn()
            self._entityCreated(result)
            result["SourceName"] = strSource
            result["TargetName"] = strTarget
            result["AssocName"] = names[1]
            source.outgoing[name] = result
            target.incoming[name] = result
            self.MetaProperty(name, "Id", "Integer", 
                                 "The unique identifier")

            # all extension source: Outgoing: add AssocCollection
            for c in source.extension.itervalues():
                c.outgoing[name] = OutgoingAssocCollection()
            # all extension target: Incoming: add AssocCollection
            for c in target.extension.itervalues():
                c.incoming[name] = IncomingAssocCollection()
        else:
            assert(0)
        return result

    def MetaProperty(self, metaEntityName, propertyName, 
                     typeName, description = ""):
        metaType = self.metaModel().metaTypes[typeName]
        assert(metaType)
        if self.metaModel().isAssocName(metaEntityName):
            metaEntity = self.metaModel().metaAssocs[metaEntityName]
        else:
            metaEntity = self.metaModel().metaClasses[metaEntityName]
        self._setWatcherOff()
        result = MetaProperty(propertyName, metaEntity, metaType, description)
        metaEntity.metaProperties[propertyName] = result
        self._setWatcherOn()
        self._entityCreated(result)

        for e in metaEntity.extension.itervalues():
             e[propertyName] = metaType["DefaultValue"]

        return result

    def MetaEnum(self, name, defaultValue, description = ""):
        self._setWatcherOff()
        result = MetaEnum(name, self.metaModel(), description)
        self.metaModel().metaTypes[name] = result
        self._setWatcherOn()
        self._entityCreated(result)
        result["DefaultValue"] = defaultValue
        return result

    def MetaType(self, name, defaultValue, description = ""):
        self._setWatcherOff()
        result = MetaType(name, self.metaModel(), description)
        self.metaModel().metaTypes[name] = result
        self._setWatcherOn()
        self._entityCreated(result)
        result["DefaultValue"] = defaultValue
        return result

    def MetaAtom(self, enumName, atomName, description = ""):
        metaEnum = self.metaModel().metaTypes[enumName]
        if metaEnum.isMetaEnum():
            self._setWatcherOff()
            metaAtom = MetaAtom(atomName, metaEnum, description)
            metaEnum.metaAtoms[atomName] = metaAtom
            self._setWatcherOn()
            self._entityCreated(metaAtom)
            return metaAtom
        return None

    def Class(self, metaClassName, desiredId = 0):
        metaClass = self.metaModel().metaClasses[metaClassName]
        assert(metaClass)
        if desiredId == 0:
            newId = self._newGuid()
        else:
            newId = desiredId
            
        # Create class
        self._setWatcherOff()
        cls = Class(metaClass)

        # Create properties
        metaClass.extension[newId] = cls
        self._createProperties(cls, metaClass.metaProperties)
        cls["Id"] = newId
        self._setWatcherOn()

        # Create outgoing links
        for metaAssocName, metaAssoc in metaClass.outgoing.iteritems():
            assert(metaAssocName == metaAssoc["Name"])
            cls.outgoing[metaAssocName] = OutgoingAssocCollection()
        
        # Create incoming links
        for metaAssocName, metaAssoc in metaClass.incoming.iteritems():
            assert(metaAssocName == metaAssoc["Name"])
            cls.incoming[metaAssocName] = IncomingAssocCollection()

        self._entityCreated(cls)
        return cls

    def Assoc(self, source, target, assocName, desiredId = 0):
        assert(source.isClass())
        assert(target.isClass())
        metaSource = source.parent
        assert(metaSource.isMetaClass())
        metaTarget = target.parent
        assert(metaTarget.isMetaClass())

        # Create assoc name
        if self.metaModel().isAssocName(assocName):
            metaAssocName = assocName
        else:
            metaAssocName = metaSource["Name"] + "." + assocName + "." + metaTarget["Name"]
        metaAssoc = self.metaModel().metaAssocs[metaAssocName]
        assert(metaAssoc)
        assert(metaAssoc.isMetaAssoc())
        if desiredId == 0:
            newId = self._newGuid()
        else:
            newId = desiredId

        # Create assoc
        self._setWatcherOff()
        assoc = Assoc(metaAssoc)
        metaAssoc.extension[newId] = assoc
        self._createProperties(assoc, metaAssoc.metaProperties)

        assoc["Id"] = newId

        # Add to incoming outgoing links
        assoc.source = source
        assoc.target = target
        source.outgoing[metaAssocName][newId] = assoc
        target.incoming[metaAssocName][newId] = assoc

        self._setWatcherOn()
        self._entityCreated(assoc)
        return assoc

    def create(self, typ, identifierPath, state):
        newObject = None
        if Entity.METAMODEL == typ:
            pass
        elif Entity.METACLASS == typ:
            newObject = self.MetaClass(identifierPath)
        elif Entity.METAASSOC == typ:
            newObject = self.MetaAssoc(identifierPath)
        elif Entity.METAPROPERTY == typ:
            assert(type(identifierPath) == tuple)
            newObject = self.MetaProperty(identifierPath[0],
                                          identifierPath[1],
                                          identifierPath[2])
        
        elif Entity.METATYPE == typ:
            newObject = self.MetaType(identifierPath)
        elif Entity.METAENUM == typ:
            newObject = self.MetaEnum(identifierPath)
        elif Entity.METAATOM == typ:
            assert(type(identifierPath) == tuple)
            newObject = self.MetaAtom(identifierPath[0], identifierPath[1])
        elif Entity.CLASS == typ:
            assert(type(identifierPath) == tuple)
            newObject = self.Class(identifierPath[0], identifierPath[1])
        elif Entity.ASSOC == typ:
            assert(type(identifierPath) == tuple)
            #state [self.copy(), self.source.identifierPath(), self.target.identifierPath()]
            source = self.metaModel().access(Entity.CLASS, state[1])
            target = self.metaModel().access(Entity.CLASS, state[2])
            newObject = self.Assoc(source, target, identifierPath[0], identifierPath[1])
        else:
            assert(0)

        if not state is None:
            data = state[0]
            for key, value in data.iteritems():
                newObject[key] = value
        return newObject
    
    # Private methods
    def _newGuid(self):
        self.metaModel().currentId += 1
        return self.metaModel().currentId        
               
    def _createProperties(self, entity, metaProperties):
        for key, metaProperty in metaProperties.iteritems():
            if key != "Id":
                assert(key == metaProperty["Name"])
                metaType = metaProperty.metaType
                entity[key] = metaType.defaultValue()

    def _setWatcherOn(self):
        if self.watcher():
            self.watcher().setOn()
    def _setWatcherOff(self):
        if self.watcher():
            self.watcher().setOff() 
    
    def _entityCreated(self, object):
        if self.watcher():
            self.watcher().entityCreated(object)

class MetaModel(Entity):
    def __init__(self, name, metaModelType, watcher = None):
        Entity.__init__(self, None)
        self.watcher = watcher
        self.transaction = TransactionManager(self)
        self.deleter = MetaModelDeleter(self)
        self.creator = MetaModelCreator(self)
        self.currentId = 0
        self.metaTypes = MetaTypeAggregation()
        self.metaClasses = MetaClassAggregation()
        self.metaAssocs = MetaAssocAggregation()
        if self.watcher:
            self.watcher.entityCreated(self)

        self["Icon"] = ""
        self["Name"] = name
        self["Type"] = metaModelType
        self["Description"] = ""
        self["FileName"] = ""
        self["UUID"] = self.uuid()
        self["MajorVersion"] = 0
        self["MinorVersion"] = 0
        
        # Define default data types
        self.MetaType("String", "", "The string data type")
        self.MetaType("Integer", "0", "The integer data type")
        self.MetaType("Boolean", "0", "The boolean data type")
        self.MetaType("Float", "0.0", "The float data type")
        self.MetaType("Picture", "", "The picture data type")
        self.MetaType("Text", "", "The string data type")
        self.MetaType("DateTime", "(2000, 1, 1, 0, 0, 0)", "The date time type")
        self.MetaType("TimeStamp", "(2000, 1, 1, 0, 0, 0)", "The TimeStamp type")
        self.MetaType("Color", "(0, 0, 0, 0)", "The Color type")
        self.MetaType("Font", "", "The Font type")
        self.MetaType("Blob", "", "The Blob type")
        self.MetaType("FileString", "", "The FileString type")
        self.MetaType("Directory", "", "The Directory type")
        self.MetaType("Size", "(0.0, 0.0)", "The size type")
        self.MetaType("Point", "(0.0, 0.0)", "The point type")
        self.MetaType("Rect", "(0.0, 0.0, 0.0, 0.0)", "The rect type")
        self.MetaType("Flags", "0", "The flags type")
        self.MetaType("Url", "", "The url type")

    def identifierPath(self):
        return 0
    
    def typ(self):
        return Entity.METAMODEL
    def metaModel(self):
        return self

    def isMetaModel(self):
        return 1

    # creation group
    def MetaClass(self, name, description = ""):
        return self.creator.MetaClass(name, description)
    def MetaAssoc(self, name, description = "", 
                  cardinality = MANY_TO_MANY,
                  aggregation = False):
        return self.creator.MetaAssoc(name, description, 
                  cardinality, aggregation)
    def MetaProperty(self, metaEntityName, propertyName, 
                     typeName, description = ""):
        return self.creator.MetaProperty(metaEntityName, propertyName,
                                         typeName, description)
    def MetaEnum(self, name, defaultValue, description = ""):
        return self.creator.MetaEnum(name, defaultValue, description)
    def MetaType(self, name, defaultValue, description = ""):
        return self.creator.MetaType(name, defaultValue, description)
    def MetaAtom(self, enumName, atomName, description = ""):
        return self.creator.MetaAtom(enumName, atomName, description)

    def Class(self, metaClassName, desiredId = 0):
        return self.creator.Class(metaClassName, desiredId)
    def Assoc(self, source, target, assocName, desiredId = 0):
        return self.creator.Assoc(source, target, assocName, desiredId)
    def create(self, typ, identifierPath, state):
        return self.creator.create(typ, identifierPath, state)

    # Deletion group
    def delete(self, object):
        return self.deleter.delete(object)
    def deleteMetaClass(self, name):
        return self.deleter.deleteMetaClass(name)
    def deleteMetaAssoc(self, name):
        return self.deleter.deleteMetaAssoc(name)
    def deleteMetaType(self, name):
        return self.deleter.deleteMetaType(name)
    def deleteMetaAtom(self, enumName, name):
        return self.deleter.deleteMetaAtom(enumName, name)
    def deleteMetaProperty(self, name):
        return self.deleter.deleteMetaProperty(name)
    def deleteClass(self, cls):
        return self.deleter.deleteClass(cls)
    def deleteAssoc(self, assoc):
        return self.deleter.deleteAssoc(assoc)

    # Iteration and searching
    def eachMetaType(self):
        return self.metaTypes.itervalues()
    def eachMetaClass(self):
        return self.metaClasses.itervalues()
    def eachMetaAssoc(self):
        return self.metaAssocs.itervalues()
    def eachClass(self, metaClassName):
        return self.metaClasses[metaClassName].eachClass()
    def eachAssoc(self, metaAssocName):
        return self.metaAssocs[metaAssocName].eachAssoc()
    def find(self, metaObjectName, metaPropertyName, value):
        metaEntity = self.getMetaEntity(metaObjectName)
        for e in metaEntity.extension.itervalues():
            if e[metaPropertyName] == value:
                return e
        return None

    def getMetaEntity(self, name):
        if self.isAssocName(name) > 0:
           return self.metaAssocs[name]
        else:
           return self.metaClasses[name]

    # Get an meta model object directly by type and identifier
    def access(self, typ, identifierPath):
        if Entity.METAMODEL == typ:
            return self
        elif Entity.METACLASS == typ:
            return self.metaClasses[identifierPath]
        elif Entity.METAASSOC == typ:
            return self.metaAssocs[identifierPath]
        elif Entity.METAPROPERTY == typ:
            assert(type(identifierPath) == tuple)
            metaEntity = self.getMetaEntity(identifierPath[0])
            return metaEntity.metaProperties[identifierPath[1]]
        elif Entity.METATYPE == typ:
            return self.metaTypes[identifierPath]
        elif Entity.METAENUM == typ:
            return self.metaTypes[identifierPath]
        elif Entity.METAATOM == typ:
            assert(type(identifierPath) == tuple)
            metaEnum = self.metaTypes[identifierPath[0]]
            return metaEnum.metaAtoms[identifierPath[1]]
        elif Entity.CLASS == typ:
            assert(type(identifierPath) == tuple)
            metaEntity = self.getMetaEntity(identifierPath[0])
            return metaEntity.extension[identifierPath[1]]
        elif Entity.ASSOC == typ:
            assert(type(identifierPath) == tuple)
            metaEntity = self.getMetaEntity(identifierPath[0])
            return metaEntity.extension[identifierPath[1]]
        else:
            assert(0)

    def isAssocName(self, entityName):
        if entityName.count(".") == 2:
            return 1
        return 0

    def uuid(self):
        #0123456789012345678901234567890123456
        #12345678-1234-5678-1234-567812345678}
        s = ''
        for c in range(35):
            if c in [8, 13, 18, 23]:
                s += "-"
            else:
                s += hex(random.randint(0,15))[2:3]
        return s

import Builder

def buildMetaModel(name, typ, watcher = None):
    builder = Builder.Builder(name, typ, watcher)
    return builder.repository


def getFirst(collection):
    for o in collection.itervalues():
        return o
    return None


class Exporter:
  def __init__(self, r = None):
    self.metaModel = r

  def save(self, filename):
    f = open(filename, "wb")
    cPickle.dump(self.metaModel, f, 2)
    f.close()

class Importer:
  def __init__(self):
    self.metaModel = None
  def load(self, filename):
    f = open(filename, "rb")
    Entity._gIsLoading = 1
    self.metaModel = cPickle.load(f)
    self.metaModel.watcher = Watcher()
    Entity._gIsLoading = 0
    f.close()
    return self.metaModel
    
  
print "MetaModel module loaded " + time.strftime("%X", time.localtime())

import Algorithms

#v=Repository.Versioner()
#rep=Repository.buildMetaModel("test", "SpiderPROJECT")
#v.updateFile("D:/Users/sn/Development/Repository/Data/TestSpider/Test1/Test1.spid", rep)

class Versioner:
    def __init__(self):
        pass
    def updateFile(self, filename, newMetaModelInstance):
        assert(newMetaModelInstance)
        i = Importer()
        oldRep = i.load(filename)
        assert(oldRep)
        Algorithms.cloneMetaModel(oldRep, newMetaModelInstance)
        exporter = Exporter(newMetaModelInstance)
        exporter.save(filename)


def DefaultRep():
  r = buildMetaModel("Test", "SpiderTST", Watcher())
  g = r.Class("Graph")
  g["Name"] = "Dummy graph"
  cs1 = r.Class("State")
  cs1["Name"] = "State1"
  cs1["Order"] = "Up"
  cs1["Color"] = "Green"
  cs2 = r.Class("State")
  cs2["Name"] = "State2"
  cs2["Order"] = "Down"
  cs2["Color"] = "Blue"
  r.Assoc(g, cs1, "Contains")
  r.Assoc(g, cs2, "Contains")
  ca1 = r.Class("Action")
  ca1["Name"] = "Action1"
  ca2 = r.Class("Action")
  ca2["Name"] = "Action2"
  ca3 = r.Class("Action")
  ca3["Name"] = "Action3"
  ah1 = r.Assoc(cs1, ca1, "Has")
  ah2 = r.Assoc(cs2, ca2, "Has")
  ah3 = r.Assoc(cs2, ca3, "Has")

  r.Class("Test")
  r.Class("Test")
  r.Class("Test")
  r.Class("Test")
  r.Class("Test")
  return r


"""
r = DefaultRep()
ca2 = r.Class("Action")
ca2["Name"] = "TestAction"
r.transaction.begin()
print "A", r.metaClasses
r.deleteMetaClass("Action")
print "B", r.metaClasses
r.transaction.rollback()
print "C", r.metaClasses
"""



"""
for mt in r.eachMetaType():
  print mt
for mc in r.eachMetaClass():
  print mc
for ma in r.eachMetaAssoc():
  print ma
for c in r.eachClass("Action"):
  print c
for a in r.eachAssoc("State.Has.Action"):
  print a
mt = r.metaTypes["Color"]
for ma in mt.eachMetaAtom():
  print ma
mc = r.metaClasses["Action"]
for mp in mc.eachMetaProperty():
  print mp
for ma in mc.eachOutgoingMetaAssoc():
  print ma
for ma in mc.eachIncomingMetaAssoc():
  print ma
for mc2 in mc.eachOutgoingMetaClass():
  print mc2
for mc2 in mc.eachIncomingMetaClass():
  print mc2
for c in mc.eachClass():
  print c

ma = r.metaAssocs["State.Has.Action"]
for mp in ma.eachMetaProperty():
  print mp
for a in ma.eachAssoc():
  print a

cs1 = getFirst(r.metaClasses["State"].extension)
ca1 = getFirst(r.metaClasses["Action"].extension)

for a in cs1.eachOutgoingAssoc("State.Has.Action"):
 print a
for a in ca1.eachIncomingAssoc("State.Has.Action"):
 print a
for c in cs1.eachOutgoingClass("State.Has.Action"):
 print c
for c in ca1.eachIncomingClass("State.Has.Action"):
 print c

r.deleteClass(getFirst(r.metaClasses["Action"].extension))
r.deleteMetaClass("State")
"""

"""
Conventions:
  Metamodel types: Spider<XXX> or Spider<XXXX>
  Instances named: xxxName or xxxxName
  General names: repName
  Metamodel classes: MetaClass mc, MetaAssoc ma, MetaType mt, MetaEnum me, MetaAtom mat
"""

